"""
Banner数据验证工具
提供Banner创建和更新时的数据验证功能
"""
import re
from urllib.parse import urlparse
from app.models.venue import Venue

class BannerValidator:
    """Banner数据验证器"""
    
    # 有效的跳转类型
    VALID_LINK_TYPES = ['external', 'internal', 'venue', 'miniprogram', 'none']
    
    # 微信小程序AppID格式（wx开头，后跟16位字符）
    APPID_PATTERN = re.compile(r'^wx[a-zA-Z0-9]{16}$')
    
    # URL格式验证
    URL_PATTERN = re.compile(
        r'^https?://'  # http:// 或 https://
        r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|'  # 域名
        r'localhost|'  # localhost
        r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'  # IP地址
        r'(?::\d+)?'  # 端口
        r'(?:/?|[/?]\S+)$', re.IGNORECASE)
    
    @classmethod
    def validate_banner_data(cls, data, is_update=False):
        """
        验证Banner数据
        
        Args:
            data: 要验证的数据字典
            is_update: 是否为更新操作
            
        Raises:
            ValueError: 当数据验证失败时
        """
        if not data or not isinstance(data, dict):
            raise ValueError("请求数据不能为空")
            
        errors = []
        
        # 基础字段验证
        if not is_update or 'title' in data:
            if not data.get('title', '').strip():
                errors.append('标题不能为空')
            elif len(data['title'].strip()) > 100:
                errors.append('标题长度不能超过100个字符')
        
        if not is_update or 'description' in data:
            description = data.get('description', '')
            if description and len(description) > 200:
                errors.append('描述长度不能超过200个字符')
        
        if not is_update or 'image_url' in data:
            if not data.get('image_url', '').strip():
                errors.append('图片不能为空')
        
        # 跳转类型验证
        link_type = data.get('link_type', 'external')
        if link_type not in cls.VALID_LINK_TYPES:
            errors.append(f'无效的跳转类型，支持的类型：{", ".join(cls.VALID_LINK_TYPES)}')
        else:
            # 根据跳转类型验证相关字段
            type_errors = cls._validate_link_fields(data, link_type)
            errors.extend(type_errors)
        
        # 排序序号验证
        if 'sort_order' in data:
            sort_order = data.get('sort_order', 0)
            if not isinstance(sort_order, int) or sort_order < 0:
                errors.append('排序序号必须为非负整数')
        
        # 状态验证
        if 'status' in data:
            status = data.get('status', 1)
            if status not in [0, 1]:
                errors.append('状态值只能为0（禁用）或1（启用）')
        
        if errors:
            raise ValueError('; '.join(errors))
    
    @classmethod
    def _validate_link_fields(cls, data, link_type):
        """验证跳转相关字段"""
        errors = []
        
        if link_type == 'external':
            # 外部链接必须有有效的URL
            link_url = data.get('link_url', '').strip()
            if not link_url:
                errors.append('外部链接类型必须提供链接地址')
            elif not cls._is_valid_url(link_url):
                errors.append('链接地址格式不正确，必须以http://或https://开头')
        
        elif link_type == 'internal':
            # 内部页面必须有页面路径
            link_target = data.get('link_target', '').strip()
            if not link_target:
                errors.append('内部页面类型必须提供页面路径')
            elif not link_target.startswith('/'):
                errors.append('内部页面路径必须以"/"开头')
        
        elif link_type == 'venue':
            # 场馆详情必须有有效的场馆ID
            link_target = data.get('link_target', '').strip()
            if not link_target:
                errors.append('场馆详情类型必须提供场馆ID')
            else:
                try:
                    venue_id = int(link_target)
                    # 检查场馆是否存在
                    if not Venue.query.get(venue_id):
                        errors.append(f'场馆ID {venue_id} 不存在')
                except ValueError:
                    errors.append('场馆ID必须为数字')
        
        elif link_type == 'miniprogram':
            # 小程序跳转必须有AppID和页面路径
            link_appid = data.get('link_appid', '').strip()
            link_target = data.get('link_target', '').strip()
            
            if not link_appid:
                errors.append('小程序跳转类型必须提供小程序AppID')
            elif not cls._is_valid_appid(link_appid):
                errors.append('小程序AppID格式不正确，应为wx开头的18位字符')
            
            if not link_target:
                errors.append('小程序跳转类型必须提供页面路径')
            elif not link_target.startswith('pages/'):
                errors.append('小程序页面路径应以"pages/"开头')
        
        elif link_type == 'none':
            # 纯展示类型不需要跳转字段，但要清空相关字段
            pass
        
        return errors
    
    @classmethod
    def _is_valid_url(cls, url):
        """验证URL格式"""
        return bool(cls.URL_PATTERN.match(url))
    
    @classmethod
    def _is_valid_appid(cls, appid):
        """验证微信小程序AppID格式"""
        return bool(cls.APPID_PATTERN.match(appid))
    
    @classmethod
    def clean_banner_data(cls, data, link_type):
        """
        根据跳转类型清理数据
        清空不需要的字段，避免存储无用数据
        """
        cleaned_data = data.copy()
        
        if link_type == 'external':
            # 外部链接：清空link_target和link_appid
            cleaned_data['link_target'] = ''
            cleaned_data['link_appid'] = ''
        
        elif link_type == 'internal':
            # 内部页面：清空link_url和link_appid
            cleaned_data['link_url'] = ''
            cleaned_data['link_appid'] = ''
        
        elif link_type == 'venue':
            # 场馆详情：清空link_url和link_appid
            cleaned_data['link_url'] = ''
            cleaned_data['link_appid'] = ''
        
        elif link_type == 'miniprogram':
            # 小程序跳转：清空link_url
            cleaned_data['link_url'] = ''
        
        elif link_type == 'none':
            # 纯展示：清空所有跳转字段
            cleaned_data['link_url'] = ''
            cleaned_data['link_target'] = ''
            cleaned_data['link_appid'] = ''
        
        return cleaned_data
    
    @classmethod
    def validate_upload_file(cls, file):
        """
        验证上传文件
        
        Args:
            file: Flask文件对象
            
        Raises:
            ValueError: 当文件验证失败时
        """
        if not file or file.filename == '':
            raise ValueError('请选择文件')
        
        # 检查文件扩展名
        allowed_extensions = {'png', 'jpg', 'jpeg', 'gif', 'webp'}
        if '.' not in file.filename:
            raise ValueError('文件名必须包含扩展名')
        
        ext = file.filename.rsplit('.', 1)[1].lower()
        if ext not in allowed_extensions:
            raise ValueError(f'不支持的文件格式，支持的格式：{", ".join(allowed_extensions)}')
        
        # 检查文件大小（4MB限制）
        file.seek(0, 2)  # 移动到文件末尾
        file_size = file.tell()
        file.seek(0)  # 重置文件指针
        
        max_size = 4 * 1024 * 1024  # 4MB
        if file_size > max_size:
            raise ValueError(f'文件大小不能超过{max_size // (1024*1024)}MB') 